const url = require('url');
const util = require('util');
const ws = require('ws');
const Cookies = require('cookies');
const Koa = require('koa');
const app = new Koa();
const path = require('path');
const bodyParser = require('koa-bodyparser');
const koaBody = require('koa-body');
const controllers = require('./controllers');
const templating = require('./templating');
const WebSocketServer = ws.Server;

const isProduction = process.env.NODE_ENV === 'production';
app.use(async (ctx, next) => {
  ctx.set('Access-Control-Allow-Origin', 'http://localhost:3000'); // 很奇怪的是，使用 * 会出现一些其他问题
  ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  ctx.set('Access-Control-Allow-Methods', 'OPTIONS,GET,HEAD,PUT,POST,DELETE,PATCH');
  ctx.set('Access-Control-Allow-Credentials', true);
  await next();
});

// log request URL:
app.use(async (ctx, next) => {
  console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
  await next();
});

// parse user from cookie:
app.use(async(ctx, next) => {
  ctx.state.user = parseUser(ctx.cookies.get('name') || '');
  await next();
});

app.use(bodyParser());
// 可获取 post json 对象
app.use(koaBody());

app.use(templating('views', {
  noCache: !isProduction,
  watch: !isProduction
}));

app.use(controllers());

let server = app.listen(3001);

function parseUser(obj) {
  if(!obj) return;
  let s = '';
  if(typeof obj == 'string'){
    s = obj;
  } else if (obj.headers) {
    let cookies = new Cookies(obj, null);
    s = cookies.get('name');
  }
  if(s){
    try{
      let user = JSON.parse(Buffer.from(s, 'base64').toString());
      console.log(`User: ${user.name}, ID: ${user.id}`);
      return user;
    } catch (e) {
      // ignore
    }
  }
}

function createWebSocketServer(server, onConnection, onMessage, onClose, onError) {
  let wss = new WebSocketServer({
    server: server
  });
  wss.broadcast = function broadcash(data) {
    wss.clients.forEach(function each(client) {
      client.send(data);
    });
  };
  onConnection = onConnection || function () {
    console.log('[WebSocket] connected.');
  };
  onMessage = onMessage || function (msg) {
    console.log('[WebSocket] message received: ' + msg);
  }
  onClose = onClose || function (code, message) {
      console.log(`[WebSocket] closed: ${code} - ${message}`);
    };
  onError = onError || function (err) {
      console.log('[WebSocket] error: ' + err);
    };
  wss.on('connection', function (ws, req) {
    let location = url.parse(req.url, true);
    console.log('[WebSocketServer] connection: ' + location.href);
    ws.on('message', onMessage);
    ws.on('close', onClose);
    ws.on('error', onError);
    if(location.pathname !== '/ws/chat'){
      ws.close(4000, 'Invalid url');
    }
    let user = parseUser(req);
    console.log(req)
    if(!user) {
      ws.close(4001, 'Invalid user');
    }
    ws.user = user;
    ws.wss = wss;
    onConnection.apply(ws);
  });
  console.log('WebSocketServer was attached.');
  return wss;
}

var messageIndex = 0;
function createMessage(type, user, data) {
  messageIndex ++;
  return JSON.stringify({
    id: messageIndex,
    type: type,
    user: user,
    data: data
  });
}

function onConnect() {
  let user = this.user;
  let msg = createMessage('join', user, `${user.name} joined.`);
  this.wss.broadcast(msg);

  let users = Array.from(this.wss.clients).map(function (client) {
    return client.user;
  });
  console.log(users)
  this.send(createMessage('list', user, users));
}

function onMessage(message) {
  console.log(message);
  if(message && message.trim()){
    let msg = createMessage('chat', this.user, message.trim());
    this.wss.broadcast(msg);
  }
}

function onClose() {
  let user = this.user;
  let msg = createMessage('left', user, `${user.name} is left.`);
  this.wss.broadcast(msg);
}

app.wss = createWebSocketServer(server, onConnect, onMessage, onClose);

console.log('app started at port 3001');